/*  
 * Copyright (c) 2010, James Daniello
 * This project is licensed under the 'New BSD' license.
 * The full text of the license can be found in the root
 * directory under the filename "LICENSE"
 */
package src.gui;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.DefaultListModel;
import java.util.ArrayList;
import java.util.HashMap;
import java.awt.Color;
import java.awt.event.WindowListener;
import java.awt.event.WindowEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.InputEvent;
import src.engine.PICAIUGameEngine;

/**
 * This class is the basic GUI class for the client code. This class displays the lobby as well as the
 * list of current users. It may also be updated to house all the different guis at some point, or else
 * the client will do this for the various guis.
 *
 */
public class PICAIUClientGUI implements MouseListener, KeyListener {

  //The parent client which houses this GUI
  private PICAIUClient client;
  //The main gui component.
  private java.awt.Container frame;       //<-- the main panel container
  private ArrayList<PICAIUGUI> guis = new ArrayList<PICAIUGUI>();
  private String gui = "";
  private PICAIUClientGUILobby lobby;
  private PICAIUClientGUILogin login;
  private PICAIUClientGUIRegistration registration;
  private PICAIUClientGUIResetPass resetPass;
  private PICAIUClientGUIResetPass2 resetPass2;
  private PICAIUClientGUIResetPass3 resetPass3;
  private PICAIUClientGUIAddServer addServer;
  private PICAIUClientGUINoServer noServer;
  private PICAIUClientGUINoServerList noServerList;
  private PICAIUClientGUINewGame newGame;
  private PICAIUClientGUIPreGame preGame;
  //the main game engine for updating and drawing
  private PICAIUGameEngine engine;
  //the model that houses the list of users
  private DefaultListModel userModel;
  //the model that houses the list of games
  private DefaultListModel gamesModel;
  //the model that houses the list of games
  private DefaultListModel gameTypesModel;
  //screen dimensions
  public static int dimX = 800, dimY = 500;
  //list of all the color schemes
  private ArrayList<PICAIUGUIScheme> colorSchemes = new ArrayList<PICAIUGUIScheme>();
  //index of current color scheme
  private int schemeIndex = 0;

  /**
   * The constructor for the PICAIUClientGUI has three parameters and constructs the gui as
   * well as sets the first colors scheme. It will accept either a JFrames or else an applet
   * and can construct a GUI around either.
   * @param frameIn    either accepts a JFrame or Applet to construct the GUI within
   * @param clientIn   the parent client which houses this GUI
   */
  public PICAIUClientGUI(java.awt.Container frameIn, PICAIUClient clientIn) {

    client = clientIn;

    userModel = new DefaultListModel();
    gamesModel = new DefaultListModel();
    gameTypesModel = new DefaultListModel();

    //init frame
    frameIn.setSize(dimX, dimY);
    if (frameIn instanceof JFrame) {
      ((JFrame) frameIn).setLocationRelativeTo(null);
      ((JFrame) frameIn).addWindowListener(new PICAIUWindowListener(this));
      ((JFrame) frameIn).setResizable(false);
      frame = ((JFrame) frameIn).getContentPane();
    } else {
      frame = frameIn;
    }



    lobby = new PICAIUClientGUILobby(frame, this);
    registration = new PICAIUClientGUIRegistration(frame, this);
    resetPass = new PICAIUClientGUIResetPass(frame, this);
    resetPass2 = new PICAIUClientGUIResetPass2(frame, this);
    resetPass3 = new PICAIUClientGUIResetPass3(frame, this);
    addServer = new PICAIUClientGUIAddServer(frame, this);
    noServer = new PICAIUClientGUINoServer(frame, this);
    noServerList = new PICAIUClientGUINoServerList(frame, this);
    newGame = new PICAIUClientGUINewGame(frame, this);
    preGame = new PICAIUClientGUIPreGame(frame, this);
    engine = new PICAIUGameEngine(frame, dimX, dimY, this);
    guis.add(lobby);
    guis.add(registration);
    guis.add(resetPass);
    guis.add(resetPass2);
    guis.add(resetPass3);
    guis.add(noServer);
    guis.add(addServer);
    guis.add(noServerList);
    guis.add(newGame);
    guis.add(preGame);



    frameIn.setVisible(true);


    //set the initial color scheme
    initColorScheme();
  }

  /**
   * This method loads all the color schemes and inserts them into the list, designated
   * 'colorSchemes'. Then is calls the method setcolorScheme, setting the initial color scheme.
   */
  private void initColorScheme() {

    //Color Schemes. (Maybe load from file later);    
    //default color scheme
    colorSchemes.add(new PICAIUGUIScheme(new Color(234, 234, 234),
            new Color(255, 255, 255),
            new Color(0, 0, 0),
            new Color(115, 115, 115)));
    //Red Color Scheme. Needs work
    colorSchemes.add(new PICAIUGUIScheme(new Color(105, 10, 10),
            new Color(30, 0, 0),
            new Color(255, 115, 115),
            new Color(255, 75, 75)));


    setColorScheme(colorSchemes.get(0));

  }

  /**
   * Returns the user list that stores the users logged into the chat
   */
  public DefaultListModel getUserModel() {
    return userModel;
  }

  /**
   * Returns the games list that stores the available games
   */
  public DefaultListModel getGamesModel() {
    return gamesModel;
  }

  /**
   * Returns the game types list that stores the available game types
   */
  public DefaultListModel getGameTypesModel() {
    return gameTypesModel;
  }

  /**
   * This method returns the PICAIUClient which houses this object.
   */
  public PICAIUClient getClient() {
    return client;
  }

  /**
   * Returns the user name, or 'handle,' of the user running the client.
   */
  public String getUserName() {
    return client.getUserName();
  }

  /**
   * This method processes the messages that the user types. If the user types in a special command,
   * such as those starting with '/', then they will be processed. Otherwise, the text is sent to the server
   * for all users to see.
   */
  public void processMessage(String input) {

    if (input.startsWith("/")) {
      if (input.equals("/color")) {
        nextColorScheme();
      } else if (input.startsWith("/me ")) {
        client.sendMessageToServer(input);
      } else {
        writeToLobby("Unknown Command: " + input);
      }
    } else {
      client.sendMessageToServer(input);
    }
  }

  /**
   * This method will add the user with the given name to the list of users present in the lobby.
   * @param name the name of the user being added to the user list in the lobby
   */
  protected void addUserToList(String name) {
    if (!userModel.contains(name)) {
      userModel.addElement(name);
      //userModel.getElementAt(userModel.indexOf(name)).
      lobby.validate();
    }
  }

  protected void addGameToList(String game) {
    if (!gamesModel.contains(game)) {
      gamesModel.addElement(game);
      //userModel.getElementAt(userModel.indexOf(name)).
      lobby.validate();
    }
  }

  protected void addGameTypeToList(String game) {
    if (!gameTypesModel.contains(game)) {
      int amount = Integer.parseInt(((String) game).substring(1, ((String) game).indexOf("-")).replaceAll(",", ""));
      if (amount <= getClient().getUserMoney()) {
        gameTypesModel.addElement(game);
      }
      //userModel.getElementAt(userModel.indexOf(name)).
    }

    newGame.getGamesList().setSelectedIndex(newGame.getGamesList().getLastVisibleIndex());
    newGame.getCreateGameButton().setEnabled(true);
    newGame.validate();

  }

  protected void removeGameFromList(String game) {
    if (gamesModel.contains(game)) {
      gamesModel.removeElement(game);
      //userModel.getElementAt(userModel.indexOf(name)).
    }
    if (gamesModel.isEmpty()) {
      lobby.disableJoin();
    }
    lobby.validate();
  }

  /**
   * This method takes the string given and displays it to the lobby area. It also
   * accomodates for automatic scrolling. If the user's scroll bar is at the bottom the
   * window will auto-scroll the new message.
   * @param message the message to be displayed
   */
  protected void writeToLobby(String message) {
    lobby.writeToLobby(message);
  }

  /**
   * This method will clear the list of users present in the lobby
   */
  protected void clearUserList() {
    userModel.clear();
    lobby.validate();
  }

  /**
   * This method will remove the user with the given name from the list of users present in the lobby.
   * @param name the name of the user being removed from the user list in the lobby
   */
  protected void removeUserFromList(String name) {
    if (userModel.contains(name) && !name.equals(getUserName())) {
      userModel.removeElement(name);
      lobby.validate();
    }
  }

  /**
   * This is a special case method for removing ones own name from the server. Initially, a user couldn't
   * be removed from the list, in the special case that a user attempted to sign on with the same name.
   * However, in the final version of the code a user will need to be verified within the database and then
   * as the only user signed in. This code may become deprecated.
   */
  protected void removeSelfFromList() {
    if (userModel.contains(getUserName())) {
      userModel.removeElement(getUserName());
      lobby.validate();
    }
  }

  /**
   * This method makes a call to the client housing this GUI and tells it to disconnect from the server.
   */
  protected void disconnectFromServer() {
    client.disconnectFromServer();
  }

  /**
   * This method makes a call to the parent to exit the client
   */
  protected void exitClient() {
    client.exitClient();
  }

  /**
   * Returns a hashmap of all the servers in the server_list.txt file loaded into the client
   */
  protected HashMap<String, Integer> getServers() {
    return client.getServers();
  }

  /**
   * Removes the specified server from the client's list
   * @param server the server to be removed
   */
  protected void removeServer(String server) {
    client.removeServer(server);
  }

  /**
   * Adds the specified server to the client's list
   * @param host the host, devoid of spaces, of the server
   * @param port the port, in whole number integers, of the server
   */
  protected void addServer(String host, int port) {

    if (login != null && !client.getServers().containsKey(host)) {
      login.addServer(host + ":" + port);
    }
    client.addServer(host, port);
    client.saveServerList();
  }

  /**
   * Returns the selected server from the client
   */
  protected String getSelectedServer() {
    return client.getSelectedServer();
  }

  /**
   * This method sets the Jframe as invisible. It is highly likely that this code is only used when exiting
   * the application altogether. it verfies that is not calling it on an applet version of the GUI.
   */
  protected void disposeOfFrame() {
    if (frame instanceof JFrame) {
      ((JFrame) frame).setVisible(false);
    }
  }

  /**
   * This method cycles to the next color scheme in the list and sets it as the current colors scheme. It
   * wraps around.
   */
  private void nextColorScheme() {
    schemeIndex++;
    if (colorSchemes.size() > schemeIndex) {
      System.out.print("has next colorscheme " + schemeIndex);
      setColorScheme(colorSchemes.get(schemeIndex));
    } else {
      schemeIndex = 0;
      System.out.print("has next colorscheme " + 0);
      setColorScheme(colorSchemes.get(schemeIndex));
    }
  }

  /**
   * This method sets the GUI's current color scheme to that of the one given.
   * @param colorScheme an instance of PICAIUGUIScheme to be used as the new color scheme
   */
  private void setColorScheme(PICAIUGUIScheme colorScheme) {

    for (PICAIUGUI gui : guis) {
      gui.setColors(colorScheme);
    }
  }

  /**
   * This method acts on which error message is sent. This may be optimized later so it doesn't input
   * a string, but rather a short, or a byte.
   */
  public void error(String error) {
    if (error.equals("DB")) {
      login.incorrectAuth();
    }
  }

  public void setResetPassword2Email(String email) {
    if (resetPass2 != null) {
      resetPass2.setResetPassword2Email(email);
    }
  }

  /**
   * This method currently takes a string and uses that command to switch out the current GUI. During optimization
   * it may be swapped out with a short or even a byte as the input parameter.
   */
  public void setGUI(String guiIn) {
    if (!guiIn.equals(gui)) {
      //clean up
      if (login != null) {
        login.disableLogin();
      }

      if (guiIn.equals("lobby")) {
        lobby.setAsGUI();
        gui = "lobby";
      } else if (guiIn.equals("add server")) {
        addServer.setAsGUI();
        gui = "add server";
      } else if (guiIn.equals("no server")) {
        noServer.setAsGUI();
        gui = "no server";
      } else if (guiIn.equals("no server file")) {
        noServerList.setAsGUI();
        gui = "no server file";
      } else if (guiIn.equals("registration")) {
        if (guis.contains(registration)) {
          guis.remove(registration);
        }
        registration = new PICAIUClientGUIRegistration(frame, this);
        guis.add(registration);
        registration.setAsGUI();
        gui = "registration";
      } else if (guiIn.equals("login")) {
        if (guis.contains(login)) {
          guis.remove(login);
        }
        login = new PICAIUClientGUILogin(frame, this);
        guis.add(login);
        login.setAsGUI();
        login.setUserAsFocus();
        gui = "login";
      } else if (guiIn.equals("login old")) {
        //login.enableLogin();
        login.setAsGUI();
        login.setPassAsFocus();
        gui = "login";
      } else if (guiIn.equals("recover")) {
        if (guis.contains(resetPass)) {
          guis.remove(resetPass);
        }
        resetPass = new PICAIUClientGUIResetPass(frame, this);
        guis.add(resetPass);
        resetPass.setAsGUI();
        gui = "recover";
      } else if (guiIn.equals("recover 2")) {
        if (guis.contains(resetPass2)) {
          guis.remove(resetPass2);
        }
        resetPass2 = new PICAIUClientGUIResetPass2(frame, this);
        guis.add(resetPass2);
        resetPass2.setAsGUI();
        gui = "recover 2";
      } else if (guiIn.equals("recover 3")) {
        resetPass3.setAsGUI();
        gui = "recover 3";
      } else if (guiIn.equals("login recalc servers")) {
        login.setAsGUI();
        login.setUserAsFocus();
        gui = "login";
      } else if (guiIn.equals("engine")) {
        engine.setAsGUI();
        gui = "engine";
      } else if (guiIn.equals("newgame")) {
        newGame.setAsGUI();
        gui = "newgame";
      } else if (guiIn.equals("pregame")) {
        preGame.setAsGUI();
        gui = "pregame";
      }


      frame.validate();
      frame.repaint();
    }
  }

  /**
   * This sets the current user in the Login screen
   */
  public void setLoginUser(String userIn) {
    login.setUser(userIn);
  }

  /**
   * Indicates that a password change has failed
   */
  public void changeFailed(String error) {
    if (resetPass2 != null) {
      resetPass2.changeFailed(error);
    }
  }

  /**
   * Indicates that a recover of username failed
   */
  public void recoverFailed(String error) {
    if (resetPass != null) {
      resetPass.recoverFailed(error);
    }
  }

  /**
   * Enables registration again with the specified error message
   */
  public void enableRegistration(String error) {
    registration.enableRegistration(error);
  }

  /**
   * This method enables the login fields
   */
  protected void enableLogin() {
    login.enableLogin();
  }

  /**
   * This method enables the login fields
   */
  protected void disableLogin() {
    login.disableLogin();
  }

  /**
   * This method updates the selected server within the client
   */
  protected void updateSelectedServer(String selectedServer) {
    client.updateSelectedServer(selectedServer);
  }

  public void mousePressed(MouseEvent e) {
    engine.addToInputQueue(e);
  }

  public void mouseReleased(MouseEvent e) {
    //engine.addToInputQueue(e);
  }

  public void mouseEntered(MouseEvent e) {
    //engine.addToInputQueue(e);
  }

  public void mouseExited(MouseEvent e) {
    //engine.addToInputQueue(e);
  }

  public void mouseClicked(MouseEvent e) {
    //engine.addToInputQueue(e);
  }

  public void keyPressed(KeyEvent e) {
    engine.addToInputQueue(e);
  }

  public void keyReleased(KeyEvent e) {
    engine.addToInputQueue(e);
  }

  public void keyTyped(KeyEvent e) {
    engine.addToInputQueue(e);
  }

  protected void setMyInfo(String string) {
    lobby.setMyInfo(string);
    newGame.setMyInfo(string);
    preGame.setMyInfo(string);
  }

  protected void setEnemyInfo(String string, String otherReady) {
    preGame.setEnemyInfo(string, otherReady);
  }

  protected void setEnemyMoney(int money) {
    engine.setEnemyMoney(money);
  }

  public void setGameTypes(String string) {
    clearGameTypes();
    String[] data = string.split("#");
    for (int i = 0; i < data.length; i++) {
      addGameTypeToList("$" + data[i]);
    }
    //newGame.setMaxGame();
  }

  public void enableGameCreation() {
    newGame.enableGameCreation();
  }

  private void clearGameTypes() {
    gamesModel.clear();
  }

  void otherUserLeftPreGame() {
    preGame.setEnemyInfo("", "false");
  }

  void clearGamesList() {
    gamesModel.clear();
    lobby.disableJoin();
    lobby.validate();
  }

  int getUserMoney() {
    return client.getUserMoney();
  }

  void addUnit(String id, String type, String worth, String health, String energy, String vision, String accuracy) {
    try {
      preGame.addUnit(id, type, worth, health, energy, vision, accuracy);
    } catch (Exception e) {
    }
  }

  void otherReady() {
    preGame.otherReady();
  }

  void removeUnitFromPreGame(String id, String type) {
    preGame.removeUnitFromPreGame(id, type);
  }

  void setNumberOfExpectedUnits(int num) {
    engine.setNumberOfAwaitingUnits(num);
  }

  void clearPreGame() {
    preGame.clearPreGame();
  }

  void initiateEngine() {
    engine = new PICAIUGameEngine(frame, dimX, dimY, this);
  }

  void addIncomingUnit(String unit) {
    engine.addIncomingUnit(unit);
  }

  void setRedTeam(boolean redTeam) {
    engine.setRedTeam(redTeam);
  }

  void setInitialEnemyPositions(String team, long id, float x, float y) {
    engine.setInitialEnemyPositions(team, id, x, y);
  }

  public void setInitialTroopsRetrieved() {
    client.sendDataToServer("ITR");
  }

  void startEngine() {
    engine.startEngine();
  }

  void updateUnit(String message) {
    engine.updateUnit(message);
  }

  boolean isInGame() {
    return engine.isRunning();
  }

  public String getEnemyName() {
    return client.getEnemyName();
  }


}

/**
 * This Listener is the WindowListener for the JFrame version of this client. It receives
 * the commands for the JFrame version of the PICAIUClientGUI.
 */
class PICAIUWindowListener implements WindowListener {

  //the parent lobby that this applies to.
  PICAIUClientGUI client;

  public PICAIUWindowListener(PICAIUClientGUI clientIn) {
    client = clientIn;
  }

  public void windowActivated(WindowEvent e) {
  }

  public void windowClosed(WindowEvent e) {
  }

  //if the window is closing disconnect and exit.
  public void windowClosing(WindowEvent e) {
    client.getClient().saveServerList();
    client.disconnectFromServer();
    client.disposeOfFrame();
    System.exit(1);
  }

  public void windowDeactivated(WindowEvent e) {
  }

  public void windowDeiconified(WindowEvent e) {
  }

  public void windowIconified(WindowEvent e) {
  }

  public void windowOpened(WindowEvent e) {
  }
}
